//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using OpenLS.Core;
using OpenLS.Core.Serialization;
using OpenLS.Spreadsheet.Collections;
using OpenLS.Spreadsheet.Data;
using OpenLS.Spreadsheet.Drawing;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Extensibility;
using OpenLS.Spreadsheet.Internals;
using OpenLS.Spreadsheet.Parsing;
using OpenLS.Spreadsheet.Pivot;
using OpenLS.Vml;
using Expression=OpenLS.Spreadsheet.Expressions.Expression;

namespace OpenLS.Spreadsheet
{
    class PostMoveAction
    {
        private List<IDependencyNode> _nodes;
        private readonly Worksheet _worksheet;

        public PostMoveAction(List<IDependencyNode> nodes, Worksheet worksheet)
        {
            this._nodes = nodes;
            _worksheet = worksheet;
        }

        public void Do()
        {
            new CalculationChain(_nodes).Calculate();
            this._worksheet.Workbook.ResetDefinedNames();

        }
    }

    ///<summary>
    /// Represents an Excel worksheet
    ///</summary>
    public partial class Worksheet : Sheet, INotifyCollectionChanged
    {
        internal double ZoomFactor // 1 == 100%
        {
            get
            {
                if (View == null)
                    return 1;
                return View.ZoomScale/100d;
            }
        }

#if DEBUG
        public bool IsValid
        {
            get
            {
                if (this.Cells.NonVolatileCells.Count > 1000)
                    return true;
                foreach (var cell in Cells.NonVolatileCells)
                {
                    if (!cell.Row.Contains(cell))
                        return false;
                }
                return this["A1"].Row.Contains((this["A1"])) || this["A1"].IsVolatile;
            }
        }

        internal string DebugString
        {
            get { return this.Cells.DebugString; }

        }
#endif

        private object _tag;

        /// <summary>
        /// Gets or sets an arbitrary object value that can be used to store custom information about this worksheet.
        /// </summary>
        public object Tag
        {
            get { return _tag; }
            set
            {
                _tag = value;
                NotifyPropertyChanged("Tag");
            }
        }


        private readonly List<SparkLineGroup> _sparkLineGroups = new List<SparkLineGroup>();

        /// <summary>
        /// Get the list of spark line groups
        /// </summary>
        public List<SparkLineGroup> SparkLineGroups
        {
            get
            {
                Contract.Ensures(Contract.Result<List<SparkLineGroup>>() != null);
                return _sparkLineGroups;
            }
        }

        internal const int MaxColumnIndex = 16383;
        internal const int MaxRowIndex = 1048575;
        private const string xmlName = "worksheet";
        private static readonly EventArgs _dummyEventArgs = new EventArgs();
        private readonly List<Break> _colBreaks = new List<Break>();
        private readonly ColumnCollection _columns;

        private readonly ObservableCollection<ConditionalFormatting> _conditionalFormatting =
            new ObservableCollection<ConditionalFormatting>();

        private readonly ObservableCollection<QueryTable> _queryTables = new ObservableCollection<QueryTable>();
        private readonly List<Break> _rowBreaks = new List<Break>();
        private readonly RowCollection _rows;
        private readonly ObservableCollection<Table> _tables = new ObservableCollection<Table>();
        private readonly List<WebPublishItem> _webPublishItems = new List<WebPublishItem>();
        private Cell _activeCell;
        private ObservableCollection<Anchor> _anchors;
        private AutoFilter _autoFilter;
        private BitmapSource _background;
        private Rect _backgroundViewport;
        private int? _bottom;
        private WorksheetPane __bottomLeftPane;
        private CellPosition _bottomRight;
        private WorksheetPane _bottomRightPane;
        private List<CellWatch> _cellWatches;
        private List<CustomProperty> _customProperties = new List<CustomProperty>();
        private List<CustomSheetView> _customSheetViews = new List<CustomSheetView>();
        private DataConsolidate _dataConsolidate;
        private ObservableCollection<DataValidation> _dataValidations;
        private double? _defaultColumnWidth;
        private List<ExtensionElement> _extensions = new List<ExtensionElement>();
        private FontMetrics _fontMetrics;


        private int _generation;
        private HeaderFooter _headerFooter;
        private List<IgnoredError> _ignoredErrors = new List<IgnoredError>();
        private VmlDrawing _legacyVmlDrawing;
        private VmlDrawing _legacyVmlDrawingHF;
        private PageMargins _pageMargins = OpenLS.Spreadsheet.PageMargins.NormalMargins;
        private PageSetup _pageSetup;
        private PhoneticProperties _phoneticPr;
        private PrintOptions _printOptions = new PrintOptions();
        private WorksheetRangeManager _rangeManager;
        private int? _right;
        private Range _selection;
        private WorksheetCalculationProperties _sheetCalcPr;
        private SheetFormatProperties _sheetFormatPr;
        private SheetProperties _sheetPr;
        private List<SortState> _sortStates = new List<SortState>();

        private WorksheetPane __topLeftPane;
        private WorksheetPane __topRightPane;
        private WorksheetView _view;
        private SheetProtection _worksheetProtection;
        internal DoubleCellBag Cells;

        internal Worksheet(Workbook workbook, string name)
            : this(workbook, new SheetInfo(name))
        {
        }

        /// <summary>
        /// Get the used range.
        /// </summary>
        public Range UsedRange
        {
            get
            {
                Contract.Ensures(Contract.Result<Range>() != null);
                if (Cells.NonVolatileCells.Count == 0)
                {
                    if (ActiveCell != null)
                        return ActiveCell.ToRange();
                    return this[0, 0].ToRange();
                }
                int minRow = Enumerable.Min(Cells.NonVolatileCells, c => c.Row.Index);
                int maxRow = Enumerable.Max(Cells.NonVolatileCells, c => c.Row.Index);
                int minColumn = Enumerable.Min(Cells.NonVolatileCells, c => c.Column.Index);
                int maxColumn = Enumerable.Max(Cells.NonVolatileCells, c => c.Column.Index);
                return CellsRange.Create(this,
                                         new RangeAddress(new CellAddress(minRow, minColumn),
                                                          new CellAddress(maxRow, maxColumn)));

            }
        }

        internal Cell GetNonVolatileCell(int row, int column)
        {
            return this.Cells.NonVolatileCells.GetItemIfAny(new CellAddress(row, column));
        }

        internal Worksheet(Workbook workbook, SheetInfo info)
            : base(workbook, info)
        {
            _columns = new ColumnCollection(this);
            _rows = new RowCollection(this);
            //NonVolatileCells = new NonVolatileCellBag(this);
            Cells = new DoubleCellBag(this);
            _view = new WorksheetView(this);
            TopLeftPane = new WorksheetPane(this);
        }

        public int Generation
        {
            get { return _generation; }
            private set
            {
                _generation = value;
                NotifyPropertyChanged("Generation");
            }
        }

        public WorksheetPane TopLeftPane
        {
            get { return __topLeftPane; }
            internal set
            {
                if (__topLeftPane == value)
                    return;
                if (__topLeftPane != null)
                    __topLeftPane.DisableEvents();
                __topLeftPane = value;
                ensureActivePane();
                NotifyPropertyChanged("TopLeftPane");
                NotifyPropertyChanged("ScrollPane");
            }
        }

        public WorksheetPane ScrollPane
        {
            get
            {
                WorksheetPane result = TopLeftPane;
                if (TopRightPane != null)
                    result = TopRightPane;
                if (BottomLeftPane != null)
                    result = BottomLeftPane;
                if (BottomRightPane != null)
                    result = BottomRightPane;
                return result;
            }
        }

        public WorksheetPane TopRightPane
        {
            get { return __topRightPane; }
            internal set
            {
                if (__topRightPane == value)
                    return;
                if (__topRightPane != null)

                    __topRightPane.DisableEvents();
                __topRightPane = value;
                ensureActivePane();
                NotifyPropertyChanged("TopRightPane");
                NotifyPropertyChanged("ScrollPane");
            }
        }

        public WorksheetPane BottomLeftPane
        {
            get { return __bottomLeftPane; }
            internal set
            {
                if (__bottomLeftPane == value)
                    return;
                if (__bottomLeftPane != null)
                    __bottomLeftPane.DisableEvents();
                __bottomLeftPane = value;
                ensureActivePane();
                NotifyPropertyChanged("BottomLeftPane");
                NotifyPropertyChanged("ScrollPane");
            }
        }

        public WorksheetPane BottomRightPane
        {
            get { return _bottomRightPane; }
            internal set
            {
                if (_bottomRightPane == value)
                    return;
                if (_bottomRightPane != null)
                    _bottomRightPane.DisableEvents();
                _bottomRightPane = value;
                ensureActivePane();
                NotifyPropertyChanged("BottomRightPane");
                NotifyPropertyChanged("ScrollPane");
            }
        }

        /// <summary>
        /// Get the call at a given address. Cell address are zero based (A1 <=> (0, 0)).
        /// </summary>
        /// <param name="address">The cell address</param>
        /// <returns>The cell </returns>
        public Cell this[CellAddress address]
        {
            get
            {
                Contract.Ensures(Contract.Result<Cell>() != null);
                return Cells[address];
            }
        }

        internal Cell EnsurePromotedCell(CellAddress address)
        {
            {
                Contract.Ensures(Contract.Result<Cell>() != null);
                Contract.Ensures(!Contract.Result<Cell>().IsVolatile);
                Contract.Ensures(Contract.Result<Cell>().Row != null);
                Contract.Ensures(Contract.Result<Cell>().Column != null);
#if SUPERDEBUG
                Contract.Ensures(Contract.Result<Cell>().Row.Contains(Contract.Result<Cell>()) );
                Contract.Ensures(Contract.Result<Cell>().Column.Contains(Contract.Result<Cell>()));
#endif
                Cell result = this.Cells.NonVolatileCells.GetItemIfAny(address);
                if (result != null)
                    return result;
                result = Cells.GetVolatileCellIfAny(address);
                if (result != null)
                {
                    result.setRawValue(null);
                    this.Promote(result);
                    return result;
                }
                result = new Cell(Rows[address.Row], this.Columns[address.Column]);
                addCell(result, false);
                return result;
            }
        }

        /// <summary>
        /// Get the cell at a given row and column index
        /// </summary>
        /// <param name="row">The index of the row, zero based</param>
        /// <param name="column">The index of the column, zero based</param>
        /// <returns>The cell</returns>
        public Cell this[int row, int column]
        {
            get
            {
                Contract.Requires(row >= 0);
                Contract.Requires(column >= 0);
                Contract.Ensures(Contract.Result<Cell>() != null);
                return this[new CellAddress(row, column)];
            }
        }

        /// <summary>
        /// Get the cell for a given label (e.g. "A1")
        /// </summary>
        /// <param name="label">The cell label</param>
        /// <returns>The cell</returns>
        public Cell this[string label]
        {
            get
            {

                Contract.Requires(label != null);
                //\\                Contract.Ensures(Contract.Result<Cell>() == null || Contract.Result<Cell>().Row.Contains(Contract.Result<Cell>()));
                CellReferenceToken rr = Workbook.NamingConvention.ParseCellReference(label);
                if (rr == null)
                    return null;
                CellReference r = rr.Project(this);
                return r.Cell;
            }
        }

        /// <summary>
        /// Get the collection of columns of the worksheet
        /// </summary>
        public ColumnCollection Columns
        {
            get
            {
                Contract.Ensures(Contract.Result<ColumnCollection>() != null);
                return _columns;
            }
            //\\private set { _columns = value; NotifyPropertyChanged("Columns"); }
        }

        public List<IgnoredError> IgnoredErrors
        {
            get { return _ignoredErrors; }
        }

        /// <summary>
        /// Get or set the view of the worksheet
        /// </summary>
        public WorksheetView View
        {
            get { return _view; }
            set { _view = value; }
        }

        internal Pane Pane
        {
            get
            {
                if (View == null)
                    return null;
                return View.Pane;
            }
        }

        /// <summary>
        /// Gets the collection of rows of the worksheet
        /// </summary>
        public RowCollection Rows
        {
            get { return _rows; }
            //\\private set { _rows = value; }
        }

        internal NonVolatileCellBag NonVolatileCells
        {
            get { return Cells.NonVolatileCells; }
        }

        //readonly Dictionary<CellAddress, WeakReference> _volatileCells = new Dictionary<CellAddress, WeakReference>(new CellAddressComparer());

        public IEnumerable<ConditionalFormatting> ConditionalFormatting
        {
            get { return _conditionalFormatting; }
        }

        public WorksheetCalculationProperties CalculationProperties
        {
            get { return _sheetCalcPr; }
            set
            {
                _sheetCalcPr = value;
                NotifyPropertyChanged("CalculationProperties");
            }
        }

        public DataConsolidate DataConsolidate
        {
            get { return _dataConsolidate; }
            set
            {
                _dataConsolidate = value;
                NotifyPropertyChanged("DataConsolidate");
            }
        }

        public List<CustomProperty> CustomProperties
        {
            get { return _customProperties; }
        }

        public int Right
        {
            set
            {
                _right = value;
                NotifyPropertyChanged("Right");
            }
            get
            {
                if (_right == null)
                {
                    int max = 0;
                    foreach (Cell cell in NonVolatileCells)
                    {
                        max = Math.Max(max, cell.Column.Index + cell.ColumnSpan - 1);
                    }
                    _right = max;
                }
                return _right.Value;
            }
        }

        public int Bottom
        {
            set
            {
                _bottom = value;
                NotifyPropertyChanged("Bottom");
            }
            get
            {
                if (_bottom == null)
                {
                    int max = 0;
                    foreach (Cell cell in NonVolatileCells)
                    {
                        max = Math.Max(max, cell.Row.Index + cell.RowSpan - 1);
                    }
                    _bottom = max;
                }
                return _bottom.Value;
            }
        }

        public CellPosition BottomRight //\\ obsolete
        {
            get
            {
                if (_bottomRight == null)
                {
                    int maxRow = 0;
                    int maxColumn = 0;
                    foreach (Cell cell in NonVolatileCells)
                    {
                        maxRow = Math.Max(maxRow, cell.Address.Row);
                        maxColumn = Math.Max(maxColumn, cell.Address.Column);
                    }
                    _bottomRight = new CellAddress(maxRow, maxColumn);
                }
                return _bottomRight;
            }
            set
            {
                _bottomRight = value;
                ChangeBottomRight();
            }
        }

        ///<summary>
        /// Get or set the active cell of the worksheet which receives keyboard message.
        ///</summary>
        public Cell ActiveCell
        {
            get { return _activeCell; }
            set
            {
                Contract.Requires(value != null);
                if (_activeCell == value)
                    return;
                _activeCell = value;
                if (ActivePane == null || !ActivePane.Contains2(value))
                {
                    foreach (WorksheetPane pane in GetPanes())
                        if (pane.Contains2(value))
                        {
                            pane.Activate();
                            break;
                        }
                }
                if (_activeCell != null && _selection != null && !_selection.Contains(_activeCell))
                    Selection = new SingleCellRange(_activeCell);
                NotifyPropertyChanged("ActiveCell");
            }
        }

        ///<summary>
        /// Get or set the active pane which receives keyboard message.
        ///</summary>
        public WorksheetPane ActivePane
        {
            get
            {
                PaneType type = PaneType.TopLeft;
                if (View.Pane != null)
                    type = View.Pane.ActivePane;
                foreach (WorksheetPane p in GetPanes())
                    if (p.PaneType == type)
                        return p;
                return null;
            }
            set
            {
                Contract.Ensures(ActivePane == value);
                WorksheetPane old = ActivePane;
                if (value != null)
                {
                    if (View.Pane != null)
                        View.Pane.ActivePane = value.PaneType;
                }
                if (old != null)
                    old.NotifyPropertyChanged("IsActive");
                if (ActivePane != null)
                    ActivePane.NotifyPropertyChanged("IsActive");
            }
        }


        ///<summary>
        /// Get or set the selection range of the worksheet. The selection range must contain the active cell. 
        ///</summary>
        ///<exception cref="ArgumentException"></exception>
        public Range Selection
        {
            get { return _selection; }
            set
            {
                Contract.Requires(value != null);
                Contract.Requires(value.Worksheet == this);
                if (_selection == value)
                    return;
                try
                {
                    //\\  Debug.WriteLine(new StackTrace());
                    InSelectionChanged = true;

                    if (value != null && value.Worksheet != this)
                        throw new ArgumentException("value");
                    _selection = value;
                    if (ActiveCell != null && !value.Contains(ActiveCell))
                        ActiveCell = value.TopLeftCell;
                    NotifyPropertyChanged("Selection");
                    foreach (WorksheetPane p in GetPanes())
                    {
                        NotifyVisiblePaneDimensions(p);
                    }
                }
                finally
                {
                    InSelectionChanged = false;
                }
            }
        }

        internal bool InSelectionChanged { get; private set; }

        /*
        public Range Highlight
        {
            get
            {
                return _highlight;
            }
            set
            {
                _highlight = value;
                NotifyPropertyChanged("Highlight");
            }
        }
        Range _highlight;*/


        /* private Range _dropTarget;

        public Range DropTarget
        {
            get { return _dropTarget; }
            set
            {
                _dropTarget = value;
                NotifyPropertyChanged("DropTarget");
            }
        }*/
        //\\


        internal double DefaultRowHeight
        {
            get
            {
                if (_sheetFormatPr != null)
                    return _sheetFormatPr.DefaultRowHeight;
                return SheetFormatProperties.GlobalDefaultRowHeight;
            }
            set
            {
                if (Workbook.UndoEnabled)
                    Workbook.UndoManager.Add(new SetDefaultRowHeight(this, DefaultRowHeight));
                if (_sheetFormatPr == null)
                    _sheetFormatPr = new SheetFormatProperties();
                _sheetFormatPr.DefaultRowHeight = value;

                NotifyPropertyChanged("DefaultRowHeight");
                foreach (Row row in Rows)
                {
                    row.NotifyPropertyChanged("Height");
                    row.NotifyPropertyChanged("ActualHeight");
                }
            }
        }


        ///<summary>
        /// Get or set the properties of the worksheet.
        ///</summary>
        public SheetProperties Properties
        {
            get { return _sheetPr; }
            set
            {
                _sheetPr = value;
                NotifyPropertyChanged("Properties");
            }
        }

        ///<summary>
        /// Get or set the header and footer of the worksheet.
        ///</summary>
        public HeaderFooter HeaderFooter
        {
            get { return _headerFooter; }
            set
            {
                _headerFooter = value;
                NotifyPropertyChanged("HeaderFooter");
            }
        }


        public ObservableCollection<DataValidation> DataValidations
        {
            get
            {
                if (_dataValidations == null)
                {
                    _dataValidations = new ObservableCollection<DataValidation>();
                    _dataValidations.CollectionChanged += _dataValidations_CollectionChanged;
                }
                return _dataValidations;
            }
        }

        private void _dataValidations_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems != null)
                foreach (DataValidation dataValidation in e.OldItems)
                    dataValidation.Worksheet = null;
            if (e.NewItems != null)
                foreach (DataValidation dataValidation in e.NewItems)
                    dataValidation.Worksheet = this;
        }


        public PhoneticProperties PhoneticProperties
        {
            get { return _phoneticPr; }
            set
            {
                _phoneticPr = value;
                NotifyPropertyChanged("PhoneticProperties");
            }
        }

        ///<summary>
        /// Get the collection of anchors of the worksheet. Anchors contain the embedded objects of the worksheet. Such objects can be charts or diagrams for example.
        ///</summary>
        public ObservableCollection<Anchor> Anchors
        {
            get
            {
                if (_anchors == null)
                {
                    _anchors = new ObservableCollection<Anchor>();
                    _anchors.CollectionChanged += OnAnchorsChanged;
                }
                return _anchors;
            }
            //set { _anchors = value; }
        }

        ///<summary>
        /// Get or set the background bitmap of the worksheet.
        ///</summary>
        public BitmapSource Background
        {
            get { return _background; }
            set
            {
                using (Workbook.CreateUndoScope(StringTable.Background))
                {
                    Workbook.AddUndoAction(_background, source => Background = source);
                    _background = value;
                    NotifyPropertyChanged("Background");
                    if (_background != null)
                        BackgroundViewport = new Rect(new Size(_background.Width, _background.Height));
                }
            }
        }

        //\\MemoryStream backgroundStream;

        public Rect BackgroundViewport
        {
            get { return _backgroundViewport; }
            private set
            {
                _backgroundViewport = value;
                NotifyPropertyChanged("BackgroundViewport");
            }
        }

        /// <summary>
        /// Get or set the protection of the worksheet.
        /// </summary>
        public SheetProtection Protection
        {
            get { return _worksheetProtection; }
            set
            {
                _worksheetProtection = value;
                NotifyPropertyChanged("Protection");
            }
        }


        internal SheetFormatProperties FormatProperties
        {
            get { return _sheetFormatPr; }
            set
            {
                _sheetFormatPr = value;
                _defaultColumnWidth = null;
            }
        }

        internal double DefaultCharacterWidth
        {
            get
            {
                if (FormatProperties == null) return SheetFormatProperties.DefaultDefaultColumnWidth;
                return FormatProperties.DefaultColumnWidth;
            }
            set
            {
                if (FormatProperties == null)
                    FormatProperties = new SheetFormatProperties();
                FormatProperties.DefaultColumnWidth = value;
                this._defaultColumnWidth = null;
            }
        }

        //\\rename

        public VmlDrawing LegacyVmlDrawing
        {
            get
            {
                if (_legacyVmlDrawing == null)
                    _legacyVmlDrawing = new VmlDrawing();
                return _legacyVmlDrawing;
            }
        }

        public VmlDrawing LegacyVmlDrawingHF
        {
            get { return _legacyVmlDrawingHF; }
        }


        public List<CellWatch> CellWatches
        {
            get { return _cellWatches; }
            set { _cellWatches = value; }
        }

        /// <summary>
        /// Get the column breaks
        /// </summary>
        public List<Break> ColumnBreaks
        {
            get { return _colBreaks; }
        }

        /// <summary>
        /// Get the row breaks
        /// </summary>
        public List<Break> RowBreaks
        {
            get { return _rowBreaks; }
        }

        /// <summary>
        /// Get the page setup of the worksheet
        /// </summary>
        public PageSetup PageSetup
        {
            get
            {
                if (_pageSetup == null)
                    PageSetup = new PageSetup();
                return _pageSetup;
            }
            set
            {
                _pageSetup = value;
                NotifyPropertyChanged("PageSetup");
            }
        }

        /// <summary>
        /// Get or set the page margins of the worksheet
        /// </summary>
        public PageMargins PageMargins
        {
            get { return _pageMargins; }
            set
            {
                _pageMargins = value;
                NotifyPropertyChanged("PageMargins");
            }
        }

        /// <summary>
        /// Get or set the print options;
        /// </summary>
        public PrintOptions PrintOptions
        {
            get { return _printOptions; }
            set
            {
                _printOptions = value;
                NotifyPropertyChanged("PrintOptions");
            }
        }

        /// <summary>
        /// Get the query tables of the worksheet
        /// </summary>
        public ObservableCollection<QueryTable> QueryTables
        {
            get { return _queryTables; }
        }

        private List<Pivot.PivotTableDefinition> _pivotTables = new List<PivotTableDefinition>();

        internal List<PivotTableDefinition> PivotTables
        {
            get { return _pivotTables; }
        }

        /// <summary>
        /// Get the tables of the worksheet
        /// </summary>
        public ObservableCollection<Table> Tables
        {
            get { return _tables; }
        }


        public List<ExtensionElement> Extensions
        {
            get { return _extensions; }
        }

        public List<CustomSheetView> CustomSheetViews
        {
            get { return _customSheetViews; }
        }


        public List<ProtectedRange> ProtectedRanges { get; set; }

        public List<SortState> SortStates
        {
            get { return _sortStates; }
        }

        /// <summary>
        /// Get or set the auto filter of the worksheet
        /// </summary>
        public AutoFilter AutoFilter
        {
            get { return _autoFilter; }
            set
            {
                if (_autoFilter == value)
                    return;
                if (value == null)
                {
                    foreach (Row row in _autoFilter.Range.GetRows())
                        row.IsVisible = true;
                }
                _autoFilter = value;

                NotifyPropertyChanged("AutoFilter");
            }
        }

        internal static DateTime ZeroDate
        {
            get { return new DateTime(1899, 12, 31); //\\1904
            }
        }

        internal WorksheetRangeManager RangeManager
        {
            get
            {
                if (_rangeManager == null)
                {
                    _rangeManager = new WorksheetRangeManager();
                }
                return _rangeManager;
            }
        }

        internal bool IsInitializing { private get; set; }

        internal double DefaultColumnWidth
        {
            get
            {
                if (_defaultColumnWidth == null)
                    _defaultColumnWidth = CharacterWidthToPixelWidth(DefaultCharacterWidth) + 5 + 1 + 1;
                //_defaultColumnWidth = CharacterWidthToPixelWidth(8) + 5 + 1 + 1;
                return _defaultColumnWidth.Value;
            }
            set
            {
                if (Workbook.UndoEnabled)
                    Workbook.UndoManager.Add(new SetDefaultColumnWidth(this, _defaultColumnWidth));
                _defaultColumnWidth = value;
            }
        }

        internal FontMetrics FontMetrics
        {
            get
            {
                if (_fontMetrics == null)
                    _fontMetrics = new FontMetrics();
                return _fontMetrics;
            }
        }

        /// <summary>
        /// Get or set the scenarios of the worksheet
        /// </summary>
        public Scenarios Scenarios { get; set; }

        internal bool IsProtected(Func<SheetProtection, bool> accessor)
        {
            if (Protection == null)
                return false;
            return accessor(Protection);
        }

        internal bool IsProtected()
        {
            return this.IsProtected(p =>true);
        }


        #region INotifyCollectionChanged Members

        /// <summary>
        /// Occurs when the non volatile cell collection changes.
        /// </summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged
        {
            add { NonVolatileCells.CollectionChanged += value; }
            remove { NonVolatileCells.CollectionChanged -= value; }
        }

        #endregion

        internal void IncrementGeneration()
        {
#if DEBUG
            string s = new StackTrace().ToString();
            int i = s.IndexOf("IncrementGeneration");
            s = s.Substring(i + 10, s.Length - i - 10);
            Contract.Assert(!s.Contains("IncrementGeneration"));
            //Contract.Assert(new StackTrace().ToString().Contains("IncrementGeneration"));
#endif
            Generation++;
        }

        internal RangeReference GetNamedRangeReference(string name)
        {
            DefinedName nn = Enumerable.FirstOrDefault(Workbook.DefinedNames, n => n.Name == name);
            return nn == null ? null : GetRangeReference(nn.Formula);
            /*            foreach (DefinedName n in Workbook.DefinedNames)
                        {
                            if (n.Name == name) //\\ check scope
                                return GetRangeReference(n.Value);
                        }
                        return null;*/
        }

        internal RangeReference GetRangeReference(string name)
        {
            Contract.Requires(name != null);
            name = name.Trim();
            var f = new CompositeFormula(name);
            if (f.HasWorksheet)
            {
                Worksheet ws = Workbook.GetWorksheet(f.WorksheetName);
                if (ws != null)
                    return ws.GetRangeReference(f.Formula);
                return null;
            }
            string rangeReferenceLabel = name;
            foreach (DefinedName n in Workbook.DefinedNames)
            {
                if (n.Name == name) //\\ check scope
                {
                    rangeReferenceLabel = n.Formula;
                }
            }
            string[] ss = name.Split(' ');
            if (ss.Length > 1)
            {
                var refs = new List<RangeReference>();
                foreach (string s in ss)
                    refs.Add(GetRangeReference(s));
                return new RangeListReference(refs);
            }
            var parser = new ExpressionParser(this, null, LocalizationMode.NonLocalized, CellReferenceMode.Dynamic);
            Expression exp1 = parser.Expression("=" + rangeReferenceLabel);
            if (exp1 is CellReferenceExpression)
            {
                var cre = (CellReferenceExpression) exp1;
                exp1 = new RangeReferenceExpression(cre.CellReference, cre.CellReference, this);
            }
            var ex = exp1 as RangeReferenceExpression;
            if (ex == null)
                return null;
            return ex.RangeReference;
        }

        /// <summary>
        /// Get a range for a given label. Labels can represent a single cell (e.g. "A1"), a set of rows (1:2), a set of columns (A:B), a region of cells (e.g. A1:B2)
        /// </summary>
        /// <param name="label">The range label</param>
        /// <returns>The range</returns>
        public Range GetRange(string label)
        {
            RangeReference e = GetRangeReference(label);
            if (e != null)
                return e.Range;
            return null;
        }

        /// <summary>
        /// Get a rectangular range of cell between two cells
        /// </summary>
        /// <param name="cell1">The top left cell of the range</param>
        /// <param name="cell2">The bottom right cell of the range</param>
        /// <returns>The range</returns>
        public Range GetRange(Cell cell1, Cell cell2)
        {
            Contract.Requires(cell1 != null);
            Contract.Requires(cell2 != null);
            if (cell1 == cell2)
                return new SingleCellRange(cell1);
            return new MultiCellsRange(cell1.Worksheet, new RangeAddress(
                                                            new CellAddress(
                                                                Math.Min(cell1.Row.Index, cell2.Row.Index),
                                                                Math.Min(cell1.Column.Index, cell2.Column.Index)),
                                                            new CellAddress(
                                                                Math.Max(cell1.Row.Index, cell2.Row.Index),
                                                                Math.Max(cell1.Column.Index, cell2.Column.Index))),RangeType.Dynamic, true);
        }

        internal SimpleRange GetRange(RangeAddress address)
        {
            Contract.Ensures(Contract.Result<SimpleRange>() != null);
            Contract.Ensures(Contract.Result<SimpleRange>().Address.Equals(address));
            if (address.IsFullRow)
            {
                if (address.IsFullColumn)
                    return new FullWorksheetRange(this);
                return new FullRowRange(Rows[address.TopLeft.Row], Rows[address.BottomRight.Row]);
            }
            if (address.IsFullColumn)
            {
                return new FullColumnRange(Columns[address.TopLeft.Column], Columns[address.BottomRight.Column]);
            }
            if (address.ColumnSpan == 1 && address.RowSpan == 1)
                return new SingleCellRange(this[address.TopLeft]);
            return new MultiCellsRange(this, address);
            //\\return Range.FromAddress(this, address);
        }

        private void ensureActivePane()
        {
            if (ActivePane == null)
                TopLeftPane.Activate();
        }

        private void ensurePaneContainsActiveCell(WorksheetPane pane)
        {
            Contract.Requires(false);
        }

        internal void SetDefaults()
        {
            ActiveCell = this[new CellAddress()];
            Selection = CellsRange.Create(this, new RangeAddress());
        }


        internal void MoveActive(NavigationDirection d)
        {
            Cell c = ActiveCell;
            if (c == null)
            {
                this[0, 0].Select();
                return;
            }
            c = c.GetNeighbor(d);
            c.Select();
        }

        internal static void SetActiveCell(Cell c)
        {
            c.Select();
        }

        internal void Promote(Cell c)
        {
#if DEBUG
            Contract.Requires(Cells.IsValid);
            Contract.Requires(c != null);
            Contract.Ensures(Cells.IsValid);
#endif
            lock (Cells.VolatileCellLock)
            {
                Cells.RemoveVolatileCell(c);
            }
            addCell(c, false);
        }

        internal void addCell(Cell cell, bool withEvent)
        {
#if DEBUG
            Contract.Requires(Cells.IsValid);
            Contract.Ensures(Cells.IsValid);
#endif
            Cells.AddNonVolatileCell(cell);
            Utilities.AddCellInRows(cell);
            Utilities.AddCellInColumns(cell);
            if (withEvent)
                OnCellAdded(cell); //\\double call
        }

        private void removeCellsInRange(Range range, Cell exceptCell, bool deleteCells)
        {
            // Contract.Ensures(!Enumerable.Any(range.GetColumns(), c => c.Contains(range.TopLeftCell)));
            // Contract.Ensures(!Enumerable.Any(range.GetRows(), c => c.Contains(range.TopLeftCell)));
            foreach (Cell cell in new List<Cell>(Enumerable.Where(range.GetNonEmptyCells(), c => c != exceptCell)))
            {
                Contract.Assert(!cell.IsVolatile);
                if (deleteCells)
                {
                    cell.Delete();
                    if (Workbook.UndoEnabled)
                        Workbook.UndoManager.Add(new RemovedCellUndoUnit(cell));
                }
                NonVolatileCells.Remove(cell);
            }
            foreach (Column column in range.GetExistingColumns())
            {
                foreach (Row row in range.GetExistingRows())
                {
                    column.RemoveAt(row.Index);
                    row.RemoveAt(column.Index);
                }
            }
            Cells.RemoveVolatileCells(range.Contains);
        }

        /// <summary>
        /// Occurs when the layout of the worksheet change, e.g. when a column is resized
        /// </summary>
        public event EventHandler LayoutChanged;

        internal void InvokeLayoutChanged(DimensionEventArgs e)
        {
            EventHandler changed = LayoutChanged;
            if (changed != null) changed(this, e);
        }


        //\\ private SheetData sheet_data;
        private static bool canMoveRange(Range range)
        {
            Contract.Requires(range != null);
            IEnumerable<Cell> ranges =
                Enumerable.Distinct(Enumerable.Where(range.GetNonEmptyCells(), c => c.ArrayFormulaRange != null));
            foreach (Cell r in ranges)
            {
                if (!range.Contains(r))
                    return false;
            }
            return true;
        }

        private static IEnumerable<Expression> getAllExpressions(Expression expression)
        {
            yield return expression;
            foreach (Expression e in Enumerable.SelectMany(expression.GetSubExpressions(), e => getAllExpressions(e)))
            {
                yield return e;
            }
        }

        internal void MoveRange(Range source, CellAddress destination)
        {
            MoveRange_(source, destination).Do();
        }

        internal PostMoveAction MoveRange_(Range source, CellAddress destination)
        {
            if (!canMoveRange(source))
                throw new UnableToModifyRangeException();
            if (this.MovingRange != null)
                MovingRange(this, new MoveRangeEventArgs(source, destination));
            IEnumerable<IDependencyNode> movedCells2 = Enumerable.As<IDependencyNode, Cell>(source.GetNonEmptyCells());
            Range destRange = source.Offseted(destination - source.TopLeftCell.Address);

            Dictionary<CellAddress, object> targetValues = Enumerable.ToDictionary(destRange.GetNonEmptyCells(),
                                                                                   c => c.Address, c => c.Value);
            IEnumerable<IDependencyNode> destCells = Enumerable.As<IDependencyNode, Cell>(destRange.GetNonEmptyCells());

            IEnumerable<IDependencyNode> allAffectedCells = Enumerable.Union(movedCells2, destCells);
            IEnumerable<IDependencyNode> allDependents = Enumerable.SelectMany(allAffectedCells, c => c.Dependents());
            var allRangeDependents =
                new List<IDependencyNode>(
                    Enumerable.As<IDependencyNode, RangeNodeBase>(Enumerable.OfType<RangeNodeBase>(allDependents)));
            IEnumerable<RangeNode> allRangeNodes =
                (Enumerable.OfType<RangeNode>(
                    Enumerable.SelectMany(GetCells(), c => c.GetDependents())));
            var allRangeNodes2 =
                new List<RangeNode>(Enumerable.Where(allRangeNodes, r => Range.Intersects(r.Range, destRange)));
            var allNodes = new List<IDependencyNode>
                (Enumerable.Distinct(
                    Enumerable.Union(Enumerable.As<IDependencyNode, IDependencyNode>(allRangeDependents),
                                     Enumerable.As<IDependencyNode, RangeNode>(allRangeNodes2))));

            IEnumerable<Cell> formulaArrayCells = Enumerable.Where(GetCells(), c => c.ArrayFormulaRange != null);
            var affectedFormulaArrayCells = new List<Cell>();
            foreach (Cell cell in formulaArrayCells)
            {
                IEnumerable<CellReferenceExpression> cellReferenceExpressions =
                    Enumerable.OfType<CellReferenceExpression>(getAllExpressions(cell.Expression));
                if (Enumerable.Any(cellReferenceExpressions,
                                   e => e.CellReference is StaticCellReference && source.Contains(e.CellReference.Cell)))
                    affectedFormulaArrayCells.Add(cell);
            }
            var cellWithExpression = Enumerable.Where(GetCells(), c => c.Expression != null);
            Func<MultiCellsRangeReference, bool> isInterestingRangeReference = delegate(MultiCellsRangeReference cre)
                                                                                   {
                                                                                       var range = cre.Range;
                                                                                       return range.Surface > 1 &&
                                                                                              source.Contains(
                                                                                                  cre.TopLeft.Cell) !=
                                                                                              source.Contains(
                                                                                                  cre.BottomRight.Cell);
                                                                                   };
            foreach (var cell in cellWithExpression)
            {
                var rangeReferenceExpressions = new List<RangeReferenceExpression>(
                    Enumerable.Where(
                        Enumerable.OfType<RangeReferenceExpression>(ExpressionHelper.GetAllExpressions(cell.Expression))
                        ,
                        rre =>
                        rre.RangeReference is MultiCellsRangeReference &&
                        isInterestingRangeReference((MultiCellsRangeReference) rre.RangeReference)));
                if (rangeReferenceExpressions.Count == 0)
                    continue;
                Debug.WriteLine("found expression to fixup in " + cell + " " +
                                StringEnumerable.Concat(rangeReferenceExpressions, ", "));
            }

            IUndoUnit moveRangeUndoUnit = null;
            if (Workbook.UndoEnabled)
                moveRangeUndoUnit = new MoveRangeUndoUnit(source.Offseted(destination - source.TopLeftCell.Address),
                                                          source.TopLeftCell.Address);
            if (Workbook.UndoEnabled)
                Workbook.AddUndoUnit(new SetSelectionUndoUnit(this.Selection));

            if (source is FullRange)
            {
                ((FullRange) source.Offseted(destination - source.TopLeftCell.Address)).CopyDimensions(
                    (FullRange) source);
            }
            fixupSharedFormulas(SharedExpressionHelper.GetSharedFormulas(source), source.Contains,
                                destination - source.TopLeftCell.Address);
            CellAddress sourceTopLeft = source.Address.TopLeft;
            var rangeCells = new List<Cell>(source.GetExistingCells());
            CellRelativeAddress diff = destination - sourceTopLeft;
            Range targetRange = source.Offseted(destination - source.TopLeftCell.Address);
            if (this.CellsRemoved != null)
            {
                var removedCells = Enumerable.Union(targetRange.GetNonEmptyCells(), source.GetNonEmptyCells());
                removedCells = Enumerable.Distinct(removedCells);
                CellsRemoved(this, new CellRegionEventArgs(new MultiCellRegion(removedCells)));
            }
            removeCellsInRange(source, null, false); //\\tocheck
            removeCellsInRange(targetRange, null, true);
            if (moveRangeUndoUnit != null)
                Workbook.AddUndoUnit(moveRangeUndoUnit);

            foreach (Cell c in rangeCells)
            {
                c.SetAddress(c.AddressUnsafe + (diff));
            }
            foreach (Cell cell in rangeCells)
            {
                Cells.AddCell(cell);
                if (CellMoved != null)
                    CellMoved(this, new CellMovedArgs(cell, cell.Address - diff));
                foreach (Column column in cell.GetColumns())
                    column.Add(cell);
                foreach (Row row in cell.GetRows())
                    row.Add(cell);
            }
            foreach (Cell cell in rangeCells)
            {
                object oldValue;
                targetValues.TryGetValue(cell.Address, out oldValue);
                this.NotifyValueChanged(cell, oldValue);
            }
            foreach (Cell cell in affectedFormulaArrayCells)
            {
                cell.ForgetDependents();
                cell.AddDependents();
                cell.ChainRecalc();
            }
            return new PostMoveAction(allNodes, this);
            //new CalculationChain(allNodes).Calculate();
            // this.Workbook.ResetDefinedNames();
        }

        internal event EventHandler<CellMovedArgs> CellMoved;

        internal void CopyRange(Range source, CellAddress destination, PasteInfo pasteInfo,
                                TernaryAction<Cell, Cell, PasteInfo> function)
        {
            Contract.Requires(source != null);
            if (source is CellsRange)
            {
                CopyCellsRange((CellsRange) source, destination, pasteInfo, function);
                return;
            }
            else
                throw new NotImplementedException();
        }

        private void CopyCellsRange(CellsRange source, CellAddress destination, PasteInfo pasteInfo,
                                    TernaryAction<Cell, Cell, PasteInfo> f)
        {
            // case 1
            int columnStart = source.Address.TopLeft.Column,
                columnEnd = source.Address.BottomRight.Column,
                columnIncrement = 1;
            int rowStart = source.Address.TopLeft.Row, rowEnd = source.Address.BottomRight.Row, rowIncrement = 1;
            bool columnBeforeRow = true;
            if (source.Address.TopLeft.Column < destination.Column)
            {
                columnStart = source.Address.BottomRight.Column;
                columnEnd = source.Address.TopLeft.Column;
                columnIncrement = -1;
            }
            else if (source.Address.TopLeft.Column == destination.Column)
            {
                columnBeforeRow = false;
                if (source.Address.TopLeft.Row < destination.Row)
                {
                    rowStart = source.Address.BottomRight.Row;
                    rowEnd = source.Address.TopLeft.Row;
                    rowIncrement = -1;
                }
                else if (source.Address.TopLeft.Row > destination.Row)
                {
                }
            }
            if (columnBeforeRow)
            {
                for (int column = columnStart; column != columnEnd + columnIncrement; column += columnIncrement)
                {
                    for (int row = rowStart; row != rowEnd + rowIncrement; row += rowIncrement)
                    {
                        Cell d = this[destination + (new CellAddress(row, column) - source.Address.TopLeft)];
                        f(source.Worksheet[new CellAddress(row, column)], d, pasteInfo);
                    }
                }
            }
            else
            {
                for (int row = rowStart; row != rowEnd + rowIncrement; row += rowIncrement)
                {
                    for (int column = columnStart; column != columnEnd + columnIncrement; column += columnIncrement)
                    {
                        Cell d = this[destination + (new CellAddress(row, column) - source.Address.TopLeft)];
                        f(source.Worksheet[new CellAddress(row, column)], d, pasteInfo);
                    }
                }
            }
        }

        /// <summary>
        /// Add a new conditional formatting
        /// </summary>
        /// <param name="conditionalFormatting">The conditional formatting</param>
        public void AddConditionalFormatting(ConditionalFormatting conditionalFormatting)
        {
            Contract.Requires(conditionalFormatting != null);
            using (Workbook.CreateUndoScope(StringTable.ConditionalFormatting))
            {
                Workbook.AddUndoAction(conditionalFormatting, RemoveConditionalFormatting);
                conditionalFormatting.Apply(Spreadsheet.ConditionalFormatting.ApplyReason.Added);
                _conditionalFormatting.Add(conditionalFormatting);
            }
        }

        /// <summary>
        /// Remove a conditional formatting
        /// </summary>
        /// <param name="conditionalFormatting">The conditional formatting</param>
        public void RemoveConditionalFormatting(ConditionalFormatting conditionalFormatting)
        {
            Contract.Requires(conditionalFormatting != null);
            using (Workbook.CreateUndoScope(StringTable.ConditionalFormatting))
            {
                Workbook.AddUndoAction(conditionalFormatting, AddConditionalFormatting);
                conditionalFormatting.UnApply();
                _conditionalFormatting.Remove(conditionalFormatting);
            }
        }

        internal void ChangeBottomRight()
        {
            NotifyPropertyChanged("BottomRight"); //\\obsolete
            NotifyPropertyChanged("Bottom");
            NotifyPropertyChanged("Right");
            //   if (BottomRightChanged != null)
            //     BottomRightChanged(this, new EventArgs());
        }

        //internal event EventHandler BottomRightChanged;

        internal IEnumerable<WorksheetPane> GetPanes()
        {
            if (TopLeftPane != null)
                yield return TopLeftPane;
            if (TopRightPane != null)
                yield return TopRightPane;
            if (BottomLeftPane != null)
                yield return BottomLeftPane;
            if (BottomRightPane != null)
                yield return BottomRightPane;
        }

        private static void NotifyVisiblePaneDimensions(WorksheetPane pane)
        {
            foreach (Column c in pane.Columns)
                c.NotifyPropertyChanged("IsSelected");
            foreach (Row c in pane.Rows)
                c.NotifyPropertyChanged("IsSelected");
        }

        private void OnAnchorsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            using (Workbook.CreateUndoScope(StringTable.Anchor))
            {
                Workbook.Document.SetDirty();
                switch (e.Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        Workbook.AddUndoUnit(new AddedAnchorUndoUnit(this, (Anchor) e.NewItems[0]));
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        Workbook.AddUndoUnit(new RemovedAnchorUndoUnit(this, (Anchor) e.OldItems[0],
                                                                       e.OldStartingIndex));
                        break;
                    default:
                        Debug.WriteLine("anchor action not implemented");
                        break;
                }
            }
        }

        /// <summary>
        /// Remove all content, cell formatting and tables from the worksheet
        /// </summary>
        public void Clear()
        {
            using (CreateUndoScope(StringTable.Clear))
            {
                foreach (Cell cell in NonVolatileCells)
                {
                    if (cell.IsVolatile)
                        continue;
                    cell.SetFormattingRecordSafe(null);
                    cell.Value = null;
                    cell.Formula = null;
                }
            }
            _tables.Clear();
        }

        private void removeCellNoEvents(Cell cell)
        {
            Cells.RemoveCellNoEvents(cell);
            //            NonVolatileCells.Remove(cell);
            removeCellFromRows(cell);
            removeCellFromColumns(cell);
        }

        private void removeCellWithEvents(Cell cell)
        {
            Cells.RemoveCellWithEvents(cell);
            //            NonVolatileCells.Remove(cell);
            removeCellFromRows(cell);
            removeCellFromColumns(cell);
        }

        private static void removeCellFromColumns(Cell cell)
        {
            foreach (Column column in cell.GetColumns())
                column.Remove(cell);
        }

        private static void removeCellFromRows(Cell cell)
        {
            foreach (Row row in cell.GetRows())
                row.Remove(cell);
        }


        internal IEnumerable<Cell> GetCells()
        {
            var already = new Collections.HashSet<Cell>();
            IEnumerable<Cell> source;
            if (this.Rows.Count < Cells.NonVolatileCells.Count)
                source = Enumerable.SelectMany(Rows, row => row.GetNonEmptyCells());
            else
            {
                source = NonVolatileCells;
            }
            foreach (Cell cell in source)
            {
                if (cell.HasSpan)
                {
                    if (already.Contains(cell))
                        continue;
                    already.Add(cell);
                }
                yield return cell;
            }
        }

        internal CellsRange GetAutoIncrementRange(Cell c)
        {
            Cell c2 = c.GetNeighbor(NavigationDirection.Left);
            if (c2 == null)
                return null;
            c2 = c2.GetNeighbor(NavigationDirection.Down);
            while (c2.HasValueExpressionOrHyperlink)
            {
                c2 = c2.GetNeighbor(NavigationDirection.Down);
                if (c2 == null)
                    return null;
            }
            c2 = c2.GetNeighbor(NavigationDirection.Right).GetNeighbor(NavigationDirection.Up);
            return new MultiCellsRange(this,
                                       new RangeAddress(c.Address, c2.Address));
        }

        internal void InvalidateBottomRight()
        {
            _bottomRight = null;
            _bottom = null;
            _right = null;
            NotifyPropertyChanged("Right");
            NotifyPropertyChanged("Bottom");
            NotifyPropertyChanged("BottomRight"); //\\obsolete
        }


        internal void OnCellsAdded(IEnumerable<Cell> cells)
        {
            EventHandler<CellEventArgs> ca = CellAdded;
            if (ca != null && !IsInitializing)
            {
                foreach (var cell in cells)
                    ca(this, new CellEventArgs(cell));
            }
            var csa = CellsAdded;
            if (csa != null && !IsInitializing)
            {
                csa(this, new ListEventArgs<Cell>(Enumerable.ToList(cells)));
            }
            foreach (var cell in cells)
                RangeManager.OnCellAdded(this, new CellEventArgs(cell));
        }

        internal void OnCellAdded(Cell cell)
        {

            Contract.Requires(cell != null);
            EventHandler<CellEventArgs> ca = CellAdded;
            if (ca != null && !IsInitializing)
                ca(this, new CellEventArgs(cell));
            var csa = CellsAdded;
            if (csa != null && !IsInitializing)
                csa(this, new ListEventArgs<Cell>(new List<Cell>() {cell}));
            RangeManager.OnCellAdded(this, new CellEventArgs(cell));
        }

        internal void AddWeakCellAdded(EventHandler<CellEventArgs> value)
        {
            Contract.Requires(value != null);
            EventHandler<CellEventArgs> weakEvent = EventHandlerHelper.MakeWeak(value, v => CellAdded -= v);
            CellAdded += weakEvent;
        }

        internal void AddWeakRangeChanged(EventHandler<RangeChangeEventArgs> value)
        {
            Contract.Requires(value != null);
            EventHandler<RangeChangeEventArgs> weakEvent = EventHandlerHelper.MakeWeak(value,
                                                                                       v => RangeChanged -= v);
            RangeChanged += weakEvent;
        }

        internal void AddWeakRangeChanging(EventHandler<RangeChangeEventArgs> value)
        {
            Contract.Requires(value != null);
            EventHandler<RangeChangeEventArgs> weakEvent = EventHandlerHelper.MakeWeak(value,
                                                                                       v => this.RangeChanging -= v);
            RangeChanging += weakEvent;
        }

        /* internal void AddWeakPropertyChanged(PropertyChangedEventHandler value)
                 {
                     Contract.Requires(value != null);
                     INotifyPropertyChanged nic = this;
                     PropertyChangedEventHandler weakEvent = EventHandlerHelper.MakeWeakPropertyChanged(value, v => nic.PropertyChanged -= v);
                     nic.PropertyChanged += weakEvent;
                 }*/

        internal void AddWeakCellMoved(EventHandler<CellMovedArgs> value)
        {
            Contract.Requires(value != null);
            EventHandler<CellMovedArgs> weakEvent = EventHandlerHelper.MakeWeak(value, v => CellMoved -= v);
            CellMoved += weakEvent;
        }

        private EventHandler<RangeChangeEventArgs> _rangeChanged;

        internal event EventHandler<RangeChangeEventArgs> RangeChanged
        {
            add { _rangeChanged = (EventHandler<RangeChangeEventArgs>) Delegate.Combine(_rangeChanged, value); }
            remove { _rangeChanged = (EventHandler<RangeChangeEventArgs>) Delegate.Remove(_rangeChanged, value); }
        }

        internal event EventHandler<RangeChangeEventArgs> RangeChanging;
#if DEBUG
        public event EventHandler<CellEventArgs> CellAdded;
#else
        internal event EventHandler<CellEventArgs> CellAdded;
#endif
        internal event EventHandler<ListEventArgs<Cell>> CellsAdded;

        internal double CharacterWidthToPixelWidth(double charWidth)
        {
            double result = Math.Truncate(
                ((256*charWidth + Math.Truncate(128/Workbook.MaximumDigitWidth))/256)*Workbook.MaximumDigitWidth);
            return result;
        }


        internal void Paste(Range range, PasteInfo pasteInfo)
        {
            using (CreateUndoScope(ApplicationCommands.Paste.Text))
            {
                Selection = PasteManager.GetPasteTarget(range, Selection);
                if (pasteInfo.Cut && range.Worksheet.guid == this.guid)
                {
                    var source = range.Projected(Selection.Worksheet, new CellRelativeAddress());
                    MoveRange(source, Selection.TopLeftCell.Address);

                }
                Contract.Assert(Selection.Worksheet == this);
                // foreach (Cell cell in Selection.GetNonEmptyCells())
                //   fns.ClearCell(cell);
                Selection.CopyFrom(range, pasteInfo);
            }
        }

        internal UndoUnitScope CreateUndoScope(string name)
        {
            return Workbook.CreateUndoScope(name);
        }

        internal UndoUnitScope CreateRangeUndoScope(Range range, CellProperty property)
        {
            return Workbook.CreateRangeUndoScope(range, property);
        }

        internal UndoUnitScope CreateUndoScope(ICompositeUndoUnit u)
        {
            return Workbook.CreateUndoScope(u);
        }

        internal void RemoveCell(Cell cell)
        {
#if DEBUG
            Contract.Requires(cell != null);
            Contract.Requires(Cells.Contains(cell));
            Contract.Ensures(!Cells.Contains(cell));
#endif
            if (Workbook.UndoEnabled)
                Workbook.AddUndoUnit(new RemovedCellUndoUnit(cell));
            RemoveCellNoUndo(cell);
        }

        private void RemoveCellNoUndo(Cell cell)
        {
#if DEBUG
            Contract.Requires(Cells.IsValid);
            Contract.Requires(cell != null);
            Contract.Requires(Cells.Contains(cell));
            Contract.Ensures(!Cells.Contains(cell));
#endif
            cell.Row.Remove(cell);
            cell.Column.Remove(cell);
            Cells.RemoveCellWithEvents(cell);
        }

        internal void AddCellAt(Cell cell, CellAddress cellAddress)
        {
#if DEBUG
            Contract.Requires(cell != null);
            Contract.Requires(!Cells.Contains(cell));
            Contract.Ensures(Cells.Contains(cell));
#endif
            if (Workbook.UndoEnabled)
                Workbook.AddUndoUnit(new AddedCellUndoUnit(cell));
            cell.Row = Rows[cellAddress.Row];
            cell.Column = Columns[cellAddress.Column];
            Cells.AddCell(cell);
            cell.Row.Add(cell);
            cell.Column.Add(cell);
        }

        internal void NotifyValueChanged(Cell cell, object oldValue)
        {
            if (this._cellvalueChangedDisabler != null)
                return;
            var handler = CellValueChanged;
            if (handler != null)
                handler(cell, new CellValueChangedEventArgs<object>(cell, oldValue, cell.Value));
            Workbook.NotifyValueChanged(cell, oldValue, cell.Value);
        }

        internal void NotifyPreviewValueChanged(Cell cell)
        {
            if (this._cellvalueChangedDisabler != null)
                return;
            var handler = PreviewCellValueChanged;
            if (handler != null)
                handler(cell, new CellEventArgs(cell));
            Workbook.NotifyPreviewValueChanged(cell);
        }
        internal void NotifyPreviewFormulaChanged(Cell cell, Expression oldExpression, Expression newExpression)
        {
            if (this._cellvalueChangedDisabler != null)
                return;
            var handler = this.PreviewCellFormulaChanged;
            if (handler != null)
                handler(cell, new CellFormulaChangedEventArgs(cell, oldExpression, newExpression));
            Workbook.NotifyPreviewFormulaChanged(cell, oldExpression, newExpression);
        }
        internal void NotifyFormulaChanged(Cell cell, Expression oldExpression, Expression newExpression)
        {
            if (this._cellvalueChangedDisabler != null)
                return;
            var handler = CellFormulaChanged;
            if (handler != null)
                handler(cell, new CellFormulaChangedEventArgs(cell, oldExpression, newExpression));
            Workbook.NotifyFormulaChanged(cell, oldExpression, newExpression);
        }
        /// <summary>
        /// Occurs when the value of a cell has changed.
        /// </summary>
        public event EventHandler<CellValueChangedEventArgs<object>> CellValueChanged;

        /// <summary>
        /// Occurs when the value of a cell has changed.
        /// </summary>
        public event EventHandler<CellFormulaChangedEventArgs> CellFormulaChanged;

        /// <summary>
        /// Occurs before the value of a cell changes.
        /// </summary>
        public event EventHandler<CellEventArgs> PreviewCellValueChanged;

        /// <summary>
        /// Occurs before the formula of a cell changes.
        /// </summary>
        public event EventHandler<CellFormulaChangedEventArgs> PreviewCellFormulaChanged;

        internal event EventHandler CellBordersOrBackgroundChanged;

        /// <summary>
        /// Calculate the entire worksheet
        /// </summary>
        public void Calculate()
        {
            foreach (Cell cell in GetCells())
            {
                cell.ChainRecalc();
            }
        }


        internal void CopyFrom(Worksheet worksheet, PasteInfo pasteInfo)
        {
            using (Workbook.CreateUndoManagerDisabler())
            {
                using (new StableSelectionScope(this))
                {
                    Selection = new FullWorksheetRange(this);
                    Paste(new FullWorksheetRange(worksheet), pasteInfo);
                }
                //\\RangeOperations.CopyCells(new FullWorksheetRange(worksheet), new FullWorksheetRange( this), fn.CopyFunction);
            }
        }

        internal override IEnumerable<CustomSheetView> GetCustomSheetViews()
        {
            return CustomSheetViews;
        }

        internal CellReference ParseCellReference(string value)
        {
            CellReferenceToken v = Workbook.NamingConvention.ParseCellReference(value);
            if (v == null)
                return null;
            return new SingleWorksheetRangeFactory(this, this, CellReferenceMode.Dynamic).GetCellReference(v);
        }

        internal RangeReference ParseRangeReference(string value)
        {
            try
            {
                if (!(value.Trim().StartsWith("=")))
                    value = "=" + value;
                var parser = new ExpressionParser(this, null, LocalizationMode.NonLocalized, CellReferenceMode.Dynamic);
                var v = parser.Expression(value) as RangeReferenceExpression;
                if (v == null)
                    return null;
                return v.RangeReference;
            }
            catch
            {
                return null;
            }
        }

        private void raiseLayoutChanged()
        {
            Rows.RaiseLayoutChanged(null);
            Columns.RaiseLayoutChanged(null);
            this.Workbook.ResetDefinedNames();
        }

        #region Nested type: UnableToModifyRangeException

        private class UnableToModifyRangeException : ApplicationException
        {
        }

        #endregion

        internal WorksheetView EnsureView()
        {
            if (this.View == null)
                View = new WorksheetView(this);
            return View;
        }

        internal SheetProperties EnsureProperties()
        {
            if (this.Properties == null)
                this.Properties = new SheetProperties();
            return Properties;
        }

        internal WorksheetPane EnsurePane(PaneType paneType)
        {
            switch (paneType)
            {
                case PaneType.TopLeft:
                    return this.TopLeftPane;
                case PaneType.TopRight:
                    if (TopRightPane == null)
                        this.TopRightPane = new WorksheetPane(this);
                    return TopRightPane;
                case PaneType.BottomLeft:
                    if (BottomLeftPane == null)
                        BottomLeftPane = new WorksheetPane(this)
                            ;
                    return BottomLeftPane;
                case PaneType.BottomRight:
                    if (BottomRightPane == null)
                        this.BottomRightPane = new WorksheetPane(this);
                    return BottomRightPane;
                default:
                    throw new NotSupportedException();
            }
        }

        internal IEnumerable<Cell> GetCells(RangeAddress rangeAddress)
        {
            var dictionary = new Dictionary<CellAddress, Cell>(new CellAddressComparer());
            foreach (CellAddress a in rangeAddress.GetCellAddresses())
            {
                Contract.Assert(rangeAddress.Contains(a));
                Cell cell = this.NonVolatileCells.GetItemIfAny(a);
                if (cell != null && !dictionary.ContainsKey(cell.Address))
                {
                    Contract.Assert(cell.GetRangeAddress().Contains(a));
                    Contract.Assert(cell.GetRangeAddress().Intersects(rangeAddress));
                    dictionary.Add(cell.Address, cell);
                }
            }
            foreach (Cell c in dictionary.Values)
            {
                Contract.Assert(c.GetRangeAddress().Intersects(rangeAddress));
                yield return c;
            }
        }

        internal class MoveRangeEventArgs : EventArgs
        {
            private readonly Range _source;
            private readonly CellAddress _destination;

            public MoveRangeEventArgs(Range source, CellAddress destination)
            {
                _source = source;
                _destination = destination;
            }

            public Range Source
            {
                get { return _source; }
            }

            public CellAddress Destination
            {
                get { return _destination; }
            }
        }

        private void OnCellsRemoved(CellRegion region)
        {
            if (CellsRemoved != null)
                CellsRemoved(this, new CellRegionEventArgs(region));
        }

        internal void OnCellBorderOrBackgroundChanged(Cell cell)
        {
            if (CellBordersOrBackgroundChanged != null)
                CellBordersOrBackgroundChanged(cell, EventArgs.Empty);
        }

        internal void DeleteCells()
        {
            Workbook.AddUndoAction(this, t => { foreach (var cell in t.GetCells()) cell.Undelete(); });
            foreach (var cell in this.GetCells())
                cell.Delete();
        }

        private class RangeChangeScope : IDisposable
        {
            private readonly Range _range;
            private readonly RangeChangeType Type;
            private readonly RangeAddress Address;
            private CellMoveOrientationHint Hint;
            private readonly Worksheet worksheet;

            public RangeChangeScope(Worksheet worksheet, Range range, RangeChangeType type, RangeAddress address,
                                    CellMoveOrientationHint hint)
            {
                worksheet.OnRangeChanging_(range, type, address, hint);
                this.worksheet = worksheet;
                this._range = range;
                this.Type = type;
                this.Address = address;
                this.Hint = hint;
            }

            public void Dispose()
            {
                this.worksheet.OnRangeChanged_(_range, this.Type, Address, Hint);
            }
        }

        private IDisposable CreateRangeChangeScope(Range range, RangeChangeType type, RangeAddress address,
                                                   CellMoveOrientationHint hint)
        {
            return new RangeChangeScope(this, range, type, address, hint);
        }

        private void OnRangeChanged_(Range range, RangeChangeType type, RangeAddress address,
                                     CellMoveOrientationHint hint)
        {
            var handler = this._rangeChanged;
            if (handler == null)
                return;
            object sender = range;
            if (sender == null)
                sender = this;
            handler(sender, new RangeChangeEventArgs(type, address, hint));
        }


        private void OnRangeChanging_(Range range, RangeChangeType type, RangeAddress address,
                                      CellMoveOrientationHint hint)
        {
            var handler = this.RangeChanging;
            if (handler == null)
                return;
            object sender = range;
            if (sender == null)
                sender = this;
            handler(sender, new RangeChangeEventArgs(type, address, hint));
        }

        internal SheetProtection EnsureProtection()
        {
            if (Protection == null)
                Protection = new SheetProtection();
            return Protection;
        }

        internal Cell EnsurePromotedCell(int row, int column)
        {
            return this.EnsurePromotedCell(new CellAddress(row, column));
        }

        private WeakReference _viewModel;

        internal object ViewModel
        {
            get {if (_viewModel == null)
                return null;
                return _viewModel.Target;
            }
            set
            {
                _viewModel = new WeakReference(value);
            }
        }
    }
}